﻿using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using Money.Plugins;
using Money.Plugins.Import;
using FRW.Common;
using FRW.Common.Configuration;
using Ionic.Zip;
using FRW.Common.Extensions;
using FRW.Common.Forms;
using Money.Configuration;
using System.Threading;
using System.Timers;

namespace Money
{
    // TODO add export option for categories and subcategories etc.
    // TODO save all the data in a zip file (password protected?)
    // TODO add association between file type and application and also add an icon for display (not easy on Windows 7 due to protection levels)
    // TODO we need to have a Dirty flag to determine what needs saved and when
    // TODO decide on auto save for data and configuration and update menus as required
    // TODO should we have a config option to delete auto backups?
    // TODO should we check if an auto backup exists that was is newer than the file?
    // TODO should auto backups live in the backup list or only for crash protection?

    public class MoneyDoc : IPluginProgress, IDataSet
    {
        public event EventHandler<EventArgs> AccountChanged;
        public event EventHandler<EventArgs> TransactionsChanged;
        public event EventHandler<EventArgs> FileChanged;

        #region Fields

        private static MoneyDoc _instance;

        private bool _isFileOpen = false;
        private bool _isDataDirty = false;
        private bool _readOnly = false;

        private Transactions _transactions = new Transactions();
        private Accounts _accounts = new Accounts();
        private Configuration<ApplicationConfiguration> _configuration;
        private ImportHandlers _importHandlers;

        private string _autoSaveFile = null;
        private bool _autoSaveExists = false;

        private FileInfo _lastFile = null;

        private IProgress _progressInterface = null;
        private string _progressText = string.Empty;

        private IGui _guiInterface = null;

        private Categories _categories = null;
        private IEnumerable<DescriptionMatch> _descriptionMatches = null;

        private Plugins.PluginHost _plugins;

        private System.Timers.Timer _backupTimer = new System.Timers.Timer();

        #endregion

        #region Properties

        public static MoneyDoc Instance
        {
            get { return _instance; }
        }

        public bool IsDataDirty
        {
            get 
            {
                bool isDataDirty = _isDataDirty;

                if (Transactions != null)
                {
                    if (Transactions.IsDataDirty)
                    {
                        isDataDirty = true;
                    }
                }

                return isDataDirty; 
            }
        }

        public bool ReadOnly
        {
            get
            {
                return _readOnly;
            }
        }

        public FileInfo LastFile
        {
            get { return _lastFile; }
        }

        public bool IsFileOpen
        {
            get { return _isFileOpen; }
        }

        public ApplicationConfiguration Configuration
        {
            get { return _configuration.Item; }
        }

        public Accounts Accounts
        {
            get { return _accounts; }
        }

        public Transactions Transactions
        {
            get { return _transactions; }
        }

        public Categories Categories
        {
            get { return _categories; }
        }

        public IEnumerable<DescriptionMatch> DescriptionMatches
        {
            get { return _descriptionMatches; }
        }

        #endregion

        #region Constructors

        public MoneyDoc()
        {
            LoadConfiguartion();

            string test = Transaction.TransactionType.Transfer.ToDescription();

            _importHandlers = new ImportHandlers();

            FileInfo lastFile;

            try
            {
                // Try and configure some basic data based on what is in the settings
                lastFile = new FileInfo(Properties.Settings.Default.LastDocument);

                if (!lastFile.Exists)
                {
                    lastFile = new FileInfo(PathManager.Instance.MyDocuments + @"\" + "Money.mzp");
                }
            }
            catch
            {
                lastFile = new FileInfo(PathManager.Instance.MyDocuments + @"\" + "Money.mzp");
            }

            _lastFile = lastFile;

            _instance = this;
        }

        #endregion

        private void FilenameChanged(string filename)
        {
            _lastFile = new FileInfo(filename);
            Money.Properties.Settings.Default.LastDocument = filename;
            Money.Properties.Settings.Default.Save();
        }

        #region Import Handlers

        private void ConfigureImportHandlers()
        {
            // load in the configuration items that the import handler may require for processing transactions
            foreach (string ihName in _importHandlers.GetHandlers())
            {
                IPluginImport ih = _importHandlers.GetHandler(ihName);
            }
        }

        public IEnumerable<string> GetImportHandlerNames()
        {
            return _importHandlers.GetHandlers();
        }

        #endregion

        #region Accounts

        private void ConfigureAccounts()
        {
        }

        public void UpdateAccount(Account account, Account newAccount)
        {
            if (!account.Name.Equals(newAccount.Name))
            {
                // Should this be done in the account with a call-back when name is changed or better yet use an account number that does not change
                AccountRenamed(account.Name, newAccount.Name);
            }

            account.Update(newAccount);

            OnAccountChanged();
        }

        public Accounts ImportAccounts(string fileName)
        {
            Accounts importedAccounts = null;

            // if we have not passed in a file name then we need to use the default option
            if (fileName != null)
            {
                FileInfo fi = new FileInfo(fileName);

                string tempDir = PathManager.CreateTemporaryFolder();

                using (ZipFile zip = new ZipFile(fileName))
                {
                    if (File.Exists(fileName))
                    {
                        // now we need to try and unzip the file to a temp location
                        zip.ExtractAll(tempDir, ExtractExistingFileAction.OverwriteSilently);

                        try
                        {
                            // now try and load the data for the application
                            importedAccounts = (Accounts)Serialize.FromXMLFile(typeof(Accounts), Path.Combine(tempDir, "accounts.xml"));
                        }
                        catch
                        {
                        }
                    }
                }
            }

            return importedAccounts;
        }

        public int AddAccounts(Accounts newAccounts)
        {
            int importCount = 0;

            foreach (Account account in newAccounts)
            {
                if (AddAccount(account) != false)
                {
                    importCount++;
                }
            }

            return importCount;
        }

        public bool AddAccount(Account account)
        {
            bool status = false;

            if (!Accounts.ToList().Exists(element => element.Name.Equals(account.Name) == true))
            {
                // TODO there should be a method for doing this that does things like guard against duplicates
                Accounts.Add(account);

                status = true;

                OnAccountChanged();
            }
            return status;
        }

        public void DeleteAccount(Account account)
        {
            // delete the account
            Accounts.Remove(account);

            OnAccountChanged();

            // delete all transactions that are linked to this account
            Transactions.RemoveAll((transaction) => transaction.Account == account.Name);

            OnTransactionChanged();

            _isDataDirty = true;
        }

        public void AccountRenamed(string oldName, string newName)
        {
            foreach (Transaction transaction in Transactions)
            {
                if (transaction.Account == oldName)
                {
                    transaction.Account = newName;
                }
            }

            _isDataDirty = true;

            OnTransactionChanged();
        }

        #endregion

        #region Configuration Support

        private void Initialise()
        {
            Transaction.SetAccounts(_accounts);
            Account.SetTransactions(_transactions);

            OnAccountChanged();
            OnTransactionChanged();
        }

        private void LoadConfiguartion()
        {
            _configuration = new Configuration<ApplicationConfiguration>();
            if (!_configuration.Load())
            {
                _configuration.LoadDefault();
            }

            // HACK first stage of restructuring the categories
            //if (_configuration.Item.Categories == null)
            //{
            //    _configuration.Item.Categories = new Categories();
            //}

            // HACK tidy up the categories so that they use the top level cats rather than sub categories.
            //TidyCategories(_configuration.Item.Categories);

            Transaction.SetCategories(_configuration.Item.Categories);

            _categories = _configuration.Item.Categories;

            _categories.CategoryRemoved += _categories_CategoryRemoved;

            _descriptionMatches = _configuration.Item.DescriptionMatches;

            _plugins = new PluginHost(this);
            PluginHost.Load();

            ConfigureBackupTimer();
        }

        public void ConfigurationChanged()
        {
            ConfigureBackupTimer();

            // TODO we need to have a clear distinction between machine settings, tool settings and settings in the data files
            // TODO we should probably just remove the import save path and always save it in the app data path?
            // check if the import save path has changed and if so we should set it in default setting for this machine

            if (!Properties.Settings.Default.ImportSavePath.Equals(_configuration.Item.ImportSavePath))
            {
                Properties.Settings.Default.ImportSavePath = _configuration.Item.ImportSavePath;
                Properties.Settings.Default.Save();
            }
        }

        private void ConfigureBackupTimer()
        {
            if (_configuration.Item.BackupInterval != 0)
            {
                _backupTimer.Interval = _configuration.Item.BackupInterval * 1000;
                _backupTimer.Elapsed += _backupTimer_Elapsed;
                _backupTimer.Start();
            }
            else
            {
                _backupTimer.Stop();
            }
        }

        void _backupTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (_transactions.IsDataDirty)
            {
                Backup(true);
                AutoSave();
            }
        }

        void _categories_CategoryRemoved(object sender, CategoryRemovedEventArgs e)
        {
            List<Transaction> transactions = (from transaction in _transactions where transaction.CategoryId == e.CategoryId select transaction).ToList();

            if(transactions != null)
            {
                foreach(Transaction transaction in transactions)
                {
                    transaction.CategoryId = Convert.ToInt32(Categories.DefaultCategories.Blank);
                }
            }
        }

        // HACK
        private void TidyCategories(Categories categories)
        {
            foreach (Category category in categories)
            {
                int parentId = category.ParentId;
                Category parent = categories.Find(c => c.Id == parentId);

                if (parent != null)
                {
                    string parentName = parent.Name;

                    Category topLevel = categories.Find(c => c.Name.Equals(parent.Name) && c.Id <= 20);

                    if (topLevel != null)
                    {
                        category.ParentId = topLevel.Id;
                    }
                }
            }
        }

        public IEnumerable<Transaction> UnmatchedTransfers()
        {
            var unmatchedTransfers = from transaction in _transactions
                                     where transaction.Type == Transaction.TransactionType.Transfer
                                        && transaction.DestinationTransactionNo == 0
                                     orderby transaction.Date ascending
                                     select transaction;

            return unmatchedTransfers;
        }

        public IEnumerable<Transaction> GetTransferMatch(Transaction sourceTransaction)
        {
            var accountFilter  = Transactions.Account(sourceTransaction.DestinationAccount);
            var amountFilter = Transactions.IsAmountBetween(sourceTransaction.Amount, sourceTransaction.Amount);
            var dateFilter = Transactions.InDateRange(sourceTransaction.Date.AddDays(-14), sourceTransaction.Date.AddDays(14));

            Func<Transaction, bool> filter = accountFilter
                                 .And(amountFilter)
                                 .And(dateFilter)
                                 .Compile();

            var newList = from transaction in _transactions.Where(filter)
                          where !transaction.Account.Equals(sourceTransaction.Account) 
                             && (transaction.Amount == -sourceTransaction.Amount)
                          orderby transaction.Date ascending
                          select transaction;

            return newList;
        }

        public void ReconcileTransferTransactions(Transaction source, Transaction destination, int score)
        {
            // TODO we need to consider if there is a special case when destination transaction is already set, we need to clear this at both sides
            if (source != null && destination != null)
            {
                source.DestinationAccount = destination.Account;
                source.DestinationTransactionNo = destination.TransactionNo;
                source.CategoryId = Convert.ToInt32(Categories.DefaultCategories.Ignore);
                if (score < 0)
                {
                    source.Comment = "Transfer #" + destination.TransactionNo + " [Manual reconciled]";
                }
                else if (score == 0)
                {
                    source.Comment = "Transfer #" + destination.TransactionNo + " [Auto reconciled - single match]";
                }
                else
                {
                    source.Comment = "Transfer #" + destination.TransactionNo + " [Auto reconciled - Score: " + score + "]";
                }

                destination.DestinationAccount = source.Account;
                destination.DestinationTransactionNo = source.TransactionNo;
                destination.Type = Transaction.TransactionType.Transfer;
                destination.CategoryId = Convert.ToInt32(Categories.DefaultCategories.Ignore);
                if (score < 0)
                {
                    destination.Comment = "Transfer #" + source.TransactionNo + " [Manual reconciled]";
                }
                else if (score == 0)
                {
                    destination.Comment = "Transfer #" + source.TransactionNo + " [Auto reconciled - single match]";
                }
                else
                {
                    destination.Comment = "Transfer #" + source.TransactionNo + " [Auto reconciled - Score: " + score + "]";
                }
            }
        }

        public void AutoReconcileTransfers(IProgress progress = null)
        {
            List<Transaction> transferTransactions = UnmatchedTransfers().ToList();

            int total = transferTransactions.Count;
            int count = 0;

            if (progress != null)
            {
                progress.ProgressText = "Reconciling Transactions";
                progress.Percentage = 0;
            }

            foreach(Transaction transaction in transferTransactions)
            {
                if (progress != null)
                {
                    if (progress.Cancelled)
                    {
                        break;
                    }
                }

                if (transaction.DestinationTransactionNo == 0)
                {
                    List<Transaction> matchSelection = GetTransferMatch(transaction).ToList();

                    if (matchSelection.Count == 1)
                    {
                        if (matchSelection[0].DestinationTransactionNo == 0)
                        {
                            ReconcileTransferTransactions(transaction, matchSelection[0],0);
                        }
                    }
                    else if (matchSelection.Count > 1)
                    {
                        // If we have multiple matches then we try and apply some logic to determine what is the best match
                        Transaction bestMatch = null;
                        int matchScore = 0;

                        foreach (Transaction matchTransaction in matchSelection)
                        {
                            int score = 0;

                            // matching transactions should have a date that is close together, weight accordingly
                            int dateDiff = Math.Abs(transaction.Date.Subtract(matchTransaction.Date).Days);
                            score += (28 - (2 * dateDiff));

                            // if the description matches the name of the account that we would be transferring to then this is a good indication
                            if (transaction.Description.Equals(matchTransaction.Account) || matchTransaction.Description.Equals(transaction.Account))
                            {
                                score += 30;
                            }
                            else if (transaction.Description.Contains(matchTransaction.Account, StringComparison.CurrentCultureIgnoreCase) || matchTransaction.Description.Contains(transaction.Account, StringComparison.CurrentCultureIgnoreCase))
                            {
                                score += 15;
                            }

                            if (transaction.Amount == -matchTransaction.Amount)
                            {
                                score += 10;
                            }

                            switch (matchTransaction.Type)
                            {
                                case Transaction.TransactionType.Transfer:
                                    score += 20;
                                    break;

                                case Transaction.TransactionType.DirectDebit:
                                    score += 10;
                                    break;

                                case Transaction.TransactionType.CashWithdrawal:
                                case Transaction.TransactionType.Cheque:
                                    score -= 10;
                                    break;

                                default:
                                    break;
                            }

                            // if the transaction is already matched then we need to de-rate it.
                            if (matchTransaction.DestinationTransactionNo != 0)
                            {
                                score -= 40;
                            }

                            // check if this transaction is a better match than our current best
                            if (score > matchScore)
                            {
                                bestMatch = matchTransaction;
                                matchScore = score;
                            }
                        }

                        if (matchScore > 0 && bestMatch != null)
                        {
                            ReconcileTransferTransactions(transaction, bestMatch, matchScore);
                        }
                        // If we have only one match then we need to try and find the best match
                    }
                    else
                    {
                        // If we get here there are no possible matches so nothing to do for now
                    }

                }
                if (progress != null)
                {
                    progress.Percentage = 100 * count++ / total;
                }
            }

            if (progress != null)
            {
                progress.Finished();
            }
        }

        public void ClearTransferLink(Transaction transaction)
        {
            if (transaction.DestinationTransactionNo != 0)
            {
                Transaction destinationTransaction = (from Transaction t in _transactions
                                                      where t.TransactionNo == transaction.DestinationTransactionNo
                                                      select t).First();

                if (destinationTransaction != null)
                {
                    destinationTransaction.DestinationAccount = "";
                    destinationTransaction.DestinationTransactionNo = 0;
                }

                transaction.DestinationAccount = "";
                transaction.DestinationTransactionNo = 0;
            }
        }

        public void SaveConfiguration()
        {
            _configuration.Save();
        }

        #endregion

        #region File Handling

        public void Reload()
        {
            Close();

            Open(_lastFile.FullName);
        }

        // TODO how will we load and save from a different area
        public void Open(string fileName = null)
        {
            // TODO handle situation if we have a autosave file

            // if we have not passed in a file name then we need to use the default option
            if (_isFileOpen == true)
            {
                throw new InvalidOperationException("File has not been closed before opening new file");
            }
            else if (fileName == null)
            {
                // TODO need to consider error handling if there is an issue in the configuration
                fileName = _lastFile.FullName;
            }


            // TODO complete check for backup file
            // Simplistic check if this is a backup file
            if (fileName.Contains("backup"))
            {
                _readOnly = true;
            }
            else
            {
                _readOnly = false;
            }

            string tempDir = PathManager.CreateTemporaryFolder();
            PathManager.Instance.DataFolder = tempDir;

            if (File.Exists(fileName))
            {
                using (ZipFile zip = new ZipFile(fileName))
                {
                    // now we need to try and unzip the file to a temp location
                    zip.ExtractAll(tempDir, ExtractExistingFileAction.OverwriteSilently);
                }

                UpdateLoadedFile(fileName);
            }
            else
            {
                // TODO for now if the file does not exist the we will create one
                throw new FileNotFoundException("Cannot locate file to open", fileName);
            }

            try
            {
                // now try and load the data for the application
                if (File.Exists(PathManager.Instance.GetDataFile("accounts.xml")))
                {
                    _accounts = (Accounts)Serialize.FromXMLFile(typeof(Accounts), PathManager.Instance.GetDataFile("accounts.xml"));
                }

                if (File.Exists(PathManager.Instance.GetDataFile("transactions.xml")))
                {
                    _transactions = (Transactions)Serialize.FromXMLFile(typeof(Transactions), PathManager.Instance.GetDataFile("transactions.xml"));
                }
                Initialise();

                // HACK
                // HACK now disabled?
                //foreach (Transaction transaction in Transactions)
                //{
                //    transaction.CategoryId = GetCategoryIdForTransaction(transaction.Category);
                //}

                // TODO without this all transactions shows as edited, should we do this another way?
                Transactions.Saved();

                _isFileOpen = true;
                OnFileChanged();

                ConfigureImportHandlers();
                ConfigureAccounts();
            }
            catch
            {
                _isFileOpen = false;
                UpdateLoadedFile("");

               // throw new FileLoadException();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fileName"></param>
        public void New(string fileName)
        {
            if (_isFileOpen)
            {
                Close();
            }

            using (ZipFile zip = new ZipFile())
            {
                zip.Save(fileName);
            }

            string tempDir = PathManager.CreateTemporaryFolder();
            PathManager.Instance.DataFolder = tempDir;

            Clear();
            Save(fileName);

            UpdateLoadedFile(fileName);

            _isFileOpen = true;
            OnFileChanged();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fileName"></param>
        private void UpdateLoadedFile(string fileName)
        {
            if (!String.IsNullOrWhiteSpace(fileName))
            {
                _lastFile = new FileInfo(fileName);
            }
            Properties.Settings.Default.LastDocument = fileName;
            Properties.Settings.Default.Save();
        }

        /// <summary>
        /// 
        /// </summary>
        public void Exit()
        {
        }

        public void AutoSave(string fileName = null)
        {
            _autoSaveFile = PathManager.Instance.ApplicationFolder + @"\~" + _lastFile.Name;

            // TODO we should maybe do some error handling here in case we cant save and maybe return an error condition or throw?
            // Save the data that we may still have in memory
            Serialize.ToXMLFile(Accounts, PathManager.Instance.GetDataFile("accounts.xml"));
            Serialize.ToXMLFile(Transactions, PathManager.Instance.GetDataFile("transactions.xml"));

            // now try and create the Money file again
            using (ZipFile zip = new ZipFile())
            {
                try
                {
                    zip.AddDirectory(PathManager.Instance.DataFolder);
                    zip.Save(_autoSaveFile);
                }
                catch (Exception e)
                {
                    // TODO add error handling for save issues
                    throw e;
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fileName"></param>
        public void Save(string fileName = null)
        {
            if (_isFileOpen)
            {
                if (fileName == null)
                {
                    fileName = _lastFile.FullName;
                }

                // TODO we should maybe do some error handling here in case we cant save and maybe return an error condition or throw?
                // Save the data that we may still have in memory
                Serialize.ToXMLFile(Accounts, PathManager.Instance.GetDataFile("accounts.xml"));
                Serialize.ToXMLFile(Transactions, PathManager.Instance.GetDataFile("transactions.xml"));

                TidyAutoSave();

                // TODO should this be enabled via a configuration option?
                Backup();

                // now try and create the Money file again
                using (ZipFile zip = new ZipFile())
                {
                    try
                    {
                        zip.AddDirectory(PathManager.Instance.DataFolder);
                        zip.Save(fileName);

                        UpdateLoadedFile(fileName);

                        _isDataDirty = false;
                        OnFileChanged();
                    }
                    catch (Exception e)
                    {
                        // TODO add error handling for save issues
                        throw e;
                    }
                }

                Transactions.Saved();
            }
        }

        /// <summary>
        /// Export data file and configuration to an export file to allow this to be loaded on to a new machine
        /// </summary>
        /// <param name="fileName"></param>
        public void ExportPackage(string fileName)
        {
            string tempFolder = PathManager.CreateTemporaryFolder();

            // add all configuration files into a zip file
            using (ZipFile zip = new ZipFile())
            {
                zip.AddDirectory(PathManager.Instance.ApplicationFolder);
                zip.Save(tempFolder + @"\configuration");
            }

            // add the currently open data file to a zip file
            using (ZipFile zip = new ZipFile())
            {
                zip.AddFile(_lastFile.FullName, @"\");
                zip.Save(tempFolder + @"\data");
            }

            // add both data files to the export package
            using (ZipFile zip = new ZipFile())
            {
                zip.AddFile(tempFolder + @"\data", @"\");
                zip.AddFile(tempFolder + @"\configuration", @"\");
                zip.Save(fileName);
            }

            // now we need to tidy up the temp folder we created
            PathManager.DeleteTemporaryFolder(tempFolder);
        }

        /// <summary>
        /// Import a money exported package onto the machine potentially overwriting data that is already on the machine
        /// </summary>
        /// <param name="fileName"></param>
        public void ImportPackage(string fileName)
        {
            string tempFolder = PathManager.CreateTemporaryFolder();

            using (ZipFile zip = new ZipFile(fileName))
            {

                zip.ExtractAll(tempFolder);
            }

            using (ZipFile zip = new ZipFile(tempFolder + @"\data"))
            {
                zip.ExtractAll(PathManager.Instance.MyDocuments, ExtractExistingFileAction.OverwriteSilently);
            }

            using (ZipFile zip = new ZipFile(tempFolder + @"\configuration"))
            {
                zip.ExtractAll(PathManager.Instance.ApplicationFolder, ExtractExistingFileAction.OverwriteSilently);
            }

            // remove the temp folder we created
            PathManager.DeleteTemporaryFolder(tempFolder);
        }

        /// <summary>
        /// Backup the current data file so that we can possibly restore it later if we have an error
        /// </summary>
        private void Backup(bool autoBackup = false)
        {
            // check if the current file exists
            string currentFile = _lastFile.FullName;

            // if the current file exists then determine the application folder
            if (File.Exists(currentFile))
            {
                string backupFolder = PathManager.Instance.ApplicationFolder + "\\backup\\data";

                // check if the backup folder exists
                if (!Directory.Exists(backupFolder))
                {
                    // create the folder
                    Directory.CreateDirectory(backupFolder);
                }

                // the folder should exist now, if not then we cannot save the backup
                if (Directory.Exists(backupFolder))
                {
                    FileInfo currentFileInfo = new FileInfo(currentFile);
                    string fileName = currentFileInfo.Name.Replace(currentFileInfo.Extension,"");

                    string backupFileName = backupFolder + @"\" + fileName + String.Format(".{0:yyyyMMdd}_{0:HHmmss}", DateTime.Now) + currentFileInfo.Extension;

                    // check if we have already saved in the last second, if so we dont need to save again and the file will exist
                    if (!File.Exists(backupFileName))
                    {
                        File.Copy(currentFile, backupFileName);

                        _configuration.Item.BackupItems.Add(new BackupItem()
                            {
                                BackupFileName = backupFileName,
                                OriginalFileName = currentFile,
                                Date = DateTime.Now,
                                AutoSave = autoBackup
                            });
                    }
                }

                // if the backup folder does not exist then create it

                // save the current file to the backup folder 
            }
        }

        public void RestoreBackup(BackupItem backup)
        {
            if (backup != null)
            {
                try
                {
                    // copy the file back to its default location
                    File.Copy(backup.BackupFileName, backup.OriginalFileName, true);

                    if (backup.OriginalFileName.Equals(_lastFile.FullName))
                    {
                        Close();
                        Open(backup.OriginalFileName);
                    }
                }
                catch
                {
                    // TODO handle error conditions where we cannot restore file
                }
            }
        }

        /// <summary>
        /// Close the file that we have open. This does not save any transactions that are unsaved.
        /// </summary>
        public void Close()
        {
            if (_isFileOpen)
            {
                _accounts = new Accounts();
                _transactions = new Transactions();
                Initialise();

                _isFileOpen = false;
                _isDataDirty = false;

                PathManager.DeleteTemporaryFolder(PathManager.Instance.DataFolder);

                TidyAutoSave();

                OnTransactionChanged();
                OnAccountChanged();
            }
        }

        private void TidyAutoSave()
        {
            // remove any autosave file we may have
            if (File.Exists(_autoSaveFile))
            {
                try
                {
                    File.Delete(_autoSaveFile);
                }
                catch
                {
                }
            }
        }

        #endregion

        public void UpdateDescriptions(string matchString, string newDescription, bool RegularExpression = false, bool SaveAsUpdate = false)
        {
            // TODO from an OO perspective it may be better if the TransactionList knew how to handle this and not here in the doc
            foreach (Transaction trans in _transactions)
            {
                if (trans.Description != newDescription)
                {
                    if (RegularExpression)
                    {
                        trans.Description = Regex.Replace(trans.Description, matchString, newDescription, RegexOptions.IgnoreCase);
                    }
                    else
                    {
                        if (trans.Description.Equals(matchString))
                        {
                            trans.Description = newDescription;
                        }
                    }
                }

                if (RegularExpression && SaveAsUpdate)
                {
                    _configuration.Item.DescriptionMatches.Add(new DescriptionMatch(matchString, newDescription));
                }
            }
        }

        public void ClearTransactions()
        {
            _transactions = new Transactions();
            Account.SetTransactions(_transactions);
            OnTransactionChanged();
        }

        public void ResetTransaction(IEnumerable<Transaction> transactions, IProgress progress = null)
        {
            Thread thread = new Thread(() => DoTransactionReset(transactions, progress));
            thread.Start();
        }

        private void DoTransactionReset(IEnumerable<Transaction> transactions, IProgress progress = null)
        {
            int total = transactions.Count();
            int count = 0;

            // TODO should we avoid using the progress form if there are less than a fixed number of records?

            if (progress != null)
            {
                progress.ProgressText = "Resetting " + total + " transactions";
                progress.Percentage = 0;
            }

            foreach (Transaction transaction in transactions)
            {
                ResetTransaction(transaction);
                count++;

                if (progress != null)
                {
                    progress.Percentage = 100 * count / total;

                    if (progress.Cancelled == true)
                    {
                        break;
                    }
                }
            }

            if (progress != null)
            {
                progress.Finished();
            }
        }

        public bool ResetTransaction(Transaction transaction)
        {
            bool result = false;

            IPluginImport importHandler = _importHandlers.GetHandler(transaction.ImportHandler);
            if (importHandler != null)
            {
                importHandler.ResetTransaction(transaction);

                // reset fields that do not come from the import handler;
                transaction.DestinationAccount = "";
                transaction.DestinationTransactionNo = 0;
                transaction.Comment = "";

                result = true;
            }

            return result;
        }

        public bool AddTransaction(Transaction transaction)
        {
            if (transaction != null)
            {
                Transactions.Add(transaction);
            }

            return true;
        }

        public bool DeleteTransaction(Transaction transaction)
        {
            if (transaction != null)
            {
                return Transactions.Remove(transaction);
            }

            return false;
        }

        public string GetImportedTransactionDetail(Transaction transaction)
        {
            string rawTransaction = "";


            IPluginImport importHandler = _importHandlers.GetHandler(transaction.ImportHandler);
            if (importHandler != null)
            {
                rawTransaction = importHandler.GetOriginalTransaction(transaction.ImportId);
            }

            return rawTransaction;
        }

        private void Clear()
        {
            _transactions = new Transactions();
            _accounts = new Accounts();

            OnTransactionChanged();
            OnAccountChanged();
        }

        #region Event Triggers
        public void OnAccountChanged()
        {
            if (AccountChanged != null)
            {
                AccountChanged(this, new EventArgs());
            }
        }

        public void OnTransactionChanged()
        {
            if (TransactionsChanged != null)
            {
                TransactionsChanged(this, new EventArgs());
            }
        }

        public void OnFileChanged()
        {
            if (FileChanged != null)
            {
                FileChanged(this, new EventArgs());
            }
        }
        #endregion

        #region Import Transactions
        public void ImportFiles(List<FileInfo> files, IProgress progress)
        {
            Thread importThread = new Thread(() => DoImportFiles(files, progress));

            importThread.Start();
        }

        private void DoImportFiles(List<FileInfo> files, IProgress progress)
        {
            int total = files.Count;
            int count = 0;

            _progressInterface = progress;

            ImportStatus importStatus = new ImportStatus();

            foreach (IPluginImport ih in _importHandlers.Handlers)
            {
                ih.ResetHandler();
                ih.SetProgressInterface(this);
            }

            foreach (FileInfo file in files)
            {
                progress.ProgressText = "Identifying valid import files";

                bool parserFound = false;

                foreach (Account account in Accounts)
                {
                    string parser = account.Parser;

                    IPluginImport importHandler = _importHandlers.GetHandler(parser);

                    if (importHandler != null)
                    {
                        if (importHandler.ValidateFile(file.FullName))
                        {
                            importHandler.AddFile(new ImportFile() { FileName = file.FullName, Account = account.Name });
                            importStatus.ImportedFiles.Add(file.FullName);
                            parserFound = true;
                        }
                    }
                }

                if (!parserFound)
                {
                    importStatus.UnknownFiles.Add(file.FullName);
                }

                progress.Percentage = 100 * count++ / total;
            }

            // TODO this should probably be refactored as we will be working with each import handler by now rather than account
            total = _accounts.Count;
            count = 0;
            foreach (Account account in Accounts)
            {
                progress.ProgressText = "Importing account - " + account.Name;
                string parser = account.Parser;
                IPluginImport importHandler = _importHandlers.GetHandler(parser);
                if (importHandler != null)
                {
                    // TODO refactoring means that the DoImport now requires account name. How will we do this now?
                    // Consider splitting into import sets with each set containing account name, handler and files being worked on
                    // doing this will require refactoring of import handlers and base class ImportTransactions.
                    importHandler.DoImport();
                    importStatus.ImportedTransactions.AddRange(importHandler.ImportedTransactions);
                    // TODO move the files to an imported file folder if an option in configuration is set
                }
                progress.Percentage = 100 * count++ / total;
            }

            if (!progress.Cancelled)
            {
                progress.Tag = importStatus;
                progress.ProgressText = "Finalising imported transactions";
                progress.Percentage = 100;

                progress.Finished();
            }
            else
            {
                CancelImport();
            }

            _progressInterface = null;

            // TODO handle the fact that we may have skipped files
        }

        public void AcceptImport(ImportStatus importStatus, IProgress progress = null)
        {
            //Thread acceptThread = new Thread(() => AcceptImportThread(importStatus));
            //acceptThread.Start();

            //if (_guiInterface != null)
            //{
            //    // TODO we need to get access to the progress required form at this point?
            //    _guiInterface.ProgressRequired();
            //}

            //acceptThread.Join();

            // TODO we are not using the above threading at present so do a simple call through

            AcceptImportThread(importStatus, progress);
        }

        // TODO we should maybe move all of this into a new thread and refresh the gui at the end?
        private void AcceptImportThread(ImportStatus importStatus, IProgress progress = null)
        {
            Transactions.AddRange(importStatus.ImportedTransactions);

            if (_configuration.Item.AutoReconcileTransfersOnImport)
            {
                AutoReconcileTransfers(progress);
            }

            _isDataDirty = true;

            // check if we have a flag set to move transactions, if so do it now
            if (_configuration.Item.MoveFileOnImport)
            {
                foreach (string file in importStatus.ImportedFiles)
                {
                    FileInfo fileInfo = new FileInfo(file);

                    if (fileInfo.Exists)
                    {
                        string saveImportPath = Properties.Settings.Default.ImportSavePath;

                        if (!Directory.Exists(saveImportPath))
                        {
                            saveImportPath = PathManager.Instance.ApplicationFolder + @"\imports";
                            Properties.Settings.Default.ImportSavePath = saveImportPath;
                            Properties.Settings.Default.Save();
                        }

                        if (!Directory.Exists(saveImportPath))
                        {
                            Directory.CreateDirectory(saveImportPath);
                        }

                        if (Directory.Exists(saveImportPath))
                        {
                            string newFileName = saveImportPath + @"\" + fileInfo.Name;

                            if (!File.Exists(newFileName))
                            {
                                File.Move(file, newFileName);
                            }
                            else
                            {
                                // TODO we should throw an error that signifies import file could not be moved to required location
                                //throw new IOException();
                            }
                        }
                    }
                }
            }

            // now save the transactions
            // TODO we should only do this if we have auto save enabled and then possibly only on a timed event?
            // Save();

            // TODO this will only be required when we have full threading of the MoneyDoc and main form complete
            //if (_guiInterface != null)
            //{
            //    _guiInterface.RefreshForm();
            //}
        }

        public void CancelImport()
        {
            foreach(Account account in Accounts)
            {
                string parser = account.Parser;

                IPluginImport importHandler = _importHandlers.GetHandler(parser);

                if (importHandler != null)
                {
                    importHandler.ImportCancelled();
                }
            }
        }
        #endregion

        public void SetGuiInterface(IGui guiInterface)
        {
            _guiInterface = guiInterface;
        }

        public int Steps
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public int Step
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public string ProgressText
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                if (_progressInterface != null)
                {
                    _progressInterface.ProgressText = value;
                }
            }
        }

        public int Percentage
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                if (_progressInterface != null)
                {
                    _progressInterface.Percentage = value;
                }
            }
        }

        public bool DeleteBackupItem(BackupItem backupItem)
        {
            bool status = true;

            _configuration.Item.BackupItems.Remove(backupItem);

            if (File.Exists(backupItem.BackupFileName))
            {
                // try and delete the backup file
                try
                {
                    File.Delete(backupItem.BackupFileName);
                }
                catch
                {
                }
            }
            else
            {
            }

            return status;
        }
    }
}
